package applib

import (
	"errors"
	"github.com/shirou/gopsutil/v3/process"
	"golang.org/x/sys/windows/registry"
	"miao-client-plugins/utils"
	"os"
	"strings"
)

type AppInfo struct {
	DisplayName     string `json:"displayName,omitempty"`
	DisplayVersion  string `json:"displayVersion,omitempty"`
	InstallLocation string `json:"installLocation,omitempty"`
	Publisher       string `json:"publisher,omitempty"`
	InstallDate     string `json:"installDate,omitempty"`
}

func getList() (li []AppInfo, err error) {
	var (
		l1 []AppInfo
		l2 []AppInfo
		l3 []AppInfo
	)
	l1, _ = readRegistry(registry.LOCAL_MACHINE, "Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")

	l2, _ = readRegistry(registry.LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall")

	l3, _ = readRegistry(registry.CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall")

	li = append(li, l1...)
	li = append(li, l2...)
	li = append(li, l3...)
	return
}

func readRegistry(node registry.Key, rp string) (li []AppInfo, err error) {
	var (
		locationKey registry.Key
		apps        []string
	)
	locationKey, err = registry.OpenKey(node, rp, registry.ALL_ACCESS)
	if err != nil {
		return
	}
	defer locationKey.Close()
	// 参数设0，枚举所有子键
	apps, err = locationKey.ReadSubKeyNames(0)
	if err != nil {
		return
	}
	for i := range apps {
		var (
			appKey          registry.Key
			displayName     string
			displayVersion  string
			installLocation string
			installDate     string
			publisher       string
		)
		appKey, _ = registry.OpenKey(node, rp+"\\"+apps[i], registry.ALL_ACCESS)
		unCount := 0
		displayName, _, err = appKey.GetStringValue("DisplayName")
		if err != nil {
			unCount++
		}
		displayVersion, _, err = appKey.GetStringValue("DisplayVersion")
		if err != nil {
			unCount++
		}
		installDate, _, err = appKey.GetStringValue("InstallDate")
		if err != nil {
			unCount++
		}
		installLocation, _, err = appKey.GetStringValue("InstallLocation")
		if err != nil {
			unCount++
		}
		publisher, _, err = appKey.GetStringValue("Publisher")
		if err != nil {
			unCount++
		}
		// 注册表中软件记录字段不是强制要求的，如果要求采集的基础信息中空白信息小于3才被视为需要采集的软件信息
		if unCount < 3 {
			li = append(li, AppInfo{
				DisplayName:     displayName,
				DisplayVersion:  displayVersion,
				InstallLocation: installLocation,
				Publisher:       publisher,
				InstallDate:     installDate,
			})
		}

		appKey.Close()
	}
	return
}

type CheckProcessResult struct {
	Name    string
	Path    string
	IsExist bool
	Num     int
}

// checkProcess 检查进程是否存在，b-true表示存在该名称的进程且路径正确；num 存在多少个这样的进程，不验证路径，只看名称
// 因为进程名称是可以存在多个的，因此需要传入exePath 或者exe所在的文件夹路径来验证。
func checkProcess(name, exePath string) (b bool, num int, err error) {
	var (
		li []*process.Process
	)
	exePath = strings.ReplaceAll(exePath, "/", "\\")
	if strings.HasSuffix(exePath, name) {
		exePath = exePath[:strings.Index(exePath, name)]
	}
	if !strings.HasSuffix(exePath, "\\") {
		exePath = exePath + "\\"
	}
	if li, err = process.Processes(); err != nil {
		return false, 0, err
	}

	for _, v := range li {
		var p *process.Process
		if p, err = process.NewProcess(v.Pid); err == nil {
			pname, _ := p.Name()
			pcwd, _ := p.Cwd()
			if pname == name {
				num++
			}
			if pcwd == exePath {
				b = true
			}
		}
	}
	return
}

// CheckByPath 检查某个文件是否存在，并返回文件MD5。
// 适合一些特殊软件，不在系统中注册，默认安装的，可以通过这种方式验证
func CheckByPath(pathStr string) (md5Str string, err error) {
	md5Str, err = utils.GetFileMd5(pathStr)
	if errors.Is(err, os.ErrNotExist) {
		return "", errors.New("文件不存在")
	}
	return
}
